/////////////////////////////////////////////////////////////////////////////
//
// (c) 2007 BinaryComponents Ltd.  All Rights Reserved.
//
// http://www.binarycomponents.com/
//
/////////////////////////////////////////////////////////////////////////////

using System;
using System.Drawing;
using System.Windows.Forms;

namespace SuperListEx
{
  public class Column
  {
    #region vars
    private string _name;
    private string _caption;
    private int _width;
    private bool _isGrouped = false;
    private bool _isVisible = true;
    private SortOrder _sortOrder = SortOrder.None;
    private SortOrder _groupSortOrder = SortOrder.Ascending;
    private ColumnItemValueAccessor _groupItemValueAccessor = null;
    #endregion

    #region Properties
    public object AccessorParameter
    {
      get;
      set;
    }

    public int ColumnHeaderIndex
    {
      get;
      set;
    }

    public ListControl Parent
    {
      get;
      set;
    }

    public Type CellType
    {
      get;
      set;
    }

    /// <summary>
    /// Comparitor for a column item. Note it may be called by
    /// separate threads.
    /// </summary>
    /// <returns></returns>
    public Comparison<object> Comparitor
    {
      get;
      set;
    }

    /// <summary>
    /// Delegate to get the value of an item
    /// </summary>
    public ColumnItemValueAccessor ColumnItemFormattedAccessor
    {
      get;
      set;
    }

    /// <summary>
    /// Delegate to convert an item into a string given this column
    /// </summary>
    public ColumnItemValueAccessor ColumnItemAccessor
    {
      get;
      set;
    }

    public ColumnItemValueAccessorWithParameter ColumnItemAccessorWithParameter
    {
      get;
      set;
    }

    public ColumnItemValueAccessorWithParameter GroupItemAccessorWithParameter
    {
      get;
      set;
    }

    /// <summary>
    /// Comparitor for a grouped column item. Note it may be called by
    /// separate threads. Default is to use Comparitor
    /// </summary>
    /// <returns></returns>
    public Comparison<object> GroupedComparitor
    {
      get;
      set;
    }

    /// <summary>
    /// Is fixed width column header
    /// </summary>
    public bool IsFixedWidth
    {
      get;
      set;
    }

    /// <summary>
    /// Warp the text to fit inside the column width
    /// </summary>
    public bool WrapText
    {
      get;
      set;
    }

    /// <summary>
    /// Header icon
    /// </summary>
    public Icon HeaderIcon
    {
      get;
      set;
    }

    /// <summary>
    /// Show the column header sort arrow.
    /// </summary>
    public bool ShowHeaderSortArrow
    {
      get;
      set;
    }

    /// <summary>
    /// Delegate that's called when the group value of an item is needed. Default behaviour if not set is
    /// to call the ColumnItemAccessor.
    /// </summary>
    public ColumnItemValueAccessor GroupItemAccessor
    {
      get
      {
        return _groupItemValueAccessor == null ? ColumnItemFormattedAccessor == null ? ColumnItemAccessor : ColumnItemFormattedAccessor : _groupItemValueAccessor;
      }
      set
      {
        _groupItemValueAccessor = value;
      }
    }

    /// <summary>
    /// When a column is dragged to the group area this property denotes what to do,
    /// either make a copy of the column or move it. The default behaviour is to move.
    /// </summary>
    public MoveToGroupBehaviour MoveBehaviour
    {
      get;
      set;
    }

    /// <summary>
    /// The name of the column.
    /// </summary>
    public string Name
    {
      get
      {
        return _name;
      }
      set
      {
        if (_name != value)
        {
          _name = value;
          OnDataChanged(WhatPropertyChanged.Name);
        }
      }
    }

    /// <summary>
    /// Current sort order of the column
    /// </summary>
    public SortOrder SortOrder
    {
      get
      {
        return _sortOrder;
      }
      set
      {
        if (_sortOrder != value)
        {
          _sortOrder = value;
          OnDataChanged(WhatPropertyChanged.SortOrder);
        }
      }
    }

    /// <summary>
    /// Grouped sort order of the column.
    /// </summary>
    public SortOrder GroupSortOrder
    {
      get
      {
        return _groupSortOrder;
      }
      set
      {
        if (_groupSortOrder != value)
        {
          _groupSortOrder = value == null ? SortOrder.Descending : value;
          OnDataChanged(WhatPropertyChanged.SortOrder);
        }
      }
    }

    /// <summary>
    /// Actual text used to display in the column
    /// </summary>
    public string Caption
    {
      get
      {
        return _caption;
      }
      set
      {
        if (_caption != value)
        {
          _caption = value;
          OnDataChanged(WhatPropertyChanged.Caption);
        }
      }
    }

    /// <summary>
    /// Width of the column
    /// </summary>
    public int Width
    {
      get
      {
        return _width;
      }
      set
      {
        if (_width != value)
        {
          _width = value;
          OnDataChanged(WhatPropertyChanged.Width);
        }
      }
    }

    /// <summary>
    /// Is the column visible on the control.
    /// </summary>
    public bool IsVisible
    {
      get
      {
        return _isVisible;
      }
      set
      {
        if (_isVisible != value)
        {
          _isVisible = value;
          OnDataChanged(WhatPropertyChanged.IsVisible);
        }
      }
    }

    /// <summary>
    /// Is the column part of grouping area within the control.
    /// </summary>
    public bool IsGrouped
    {
      get
      {
        return _isGrouped;
      }
      set
      {
        if (_isGrouped != value)
        {
          _isGrouped = value;
          OnDataChanged(WhatPropertyChanged.IsGroup);
        }
      }
    }

    /// <summary>
    /// Alignment of the column text within the column header.
    /// </summary>
    public Alignment Alignment
    {
      get;
      set;
    }

    /// <summary>
    /// Vertical alignment of the column text within the column header.
    /// </summary>
    public VerticalAlignment VerticalAlignment
    {
      get;
      set;
    }
    #endregion

    #region Events
    /// <summary>
    /// Event that gets fired when something in the column changes.
    /// </summary>
    public event ColumnDataChangedHandler DataChanged;
    #endregion

    #region Constructor
    private Column()
    {
      MoveBehaviour = MoveToGroupBehaviour.Move;
      Alignment = Alignment.Left;
      VerticalAlignment = VerticalAlignment.Center;
      IsFixedWidth = false;
      ShowHeaderSortArrow = true;
      WrapText = false;
      ColumnHeaderIndex = 0;
    }

    /// <summary>
    /// Constructs a basic column
    /// </summary>
    /// <param name="name">Name of column, this can be used to identify the column across languages being used where caption will be different</param>
    /// <param name="caption">The caption used on the header.</param>
    /// <param name="width">Initial width of the column</param>
    public Column(string name, string caption, int width, ColumnItemValueAccessor columnItemValueAccessor)
      : this()
    {
      if (name == null)
      {
        throw new ArgumentNullException("name");
      }
      if (caption == null)
      {
        throw new ArgumentNullException("caption");
      }
      if (width < 0)
      {
        throw new ArgumentOutOfRangeException("width");
      }

      _name = name;
      _caption = caption;
      _width = width;

      ColumnItemAccessorWithParameter = null;
      ColumnItemAccessor = columnItemValueAccessor;
      AccessorParameter = null;

      Comparitor = ColumnItemComparer;
      GroupedComparitor = GroupItemComparer;
    }

    public Column(string name, string caption, int width, object parameter, ColumnItemValueAccessorWithParameter columnItemValueAccessorWithParameter)
      : this()
    {
      if (name == null)
      {
        throw new ArgumentNullException("name");
      }
      if (caption == null)
      {
        throw new ArgumentNullException("caption");
      }
      if (width < 0)
      {
        throw new ArgumentOutOfRangeException("width");
      }

      _name = name;
      _caption = caption;
      _width = width;

      ColumnItemAccessorWithParameter = columnItemValueAccessorWithParameter;
      ColumnItemAccessor = null;
      AccessorParameter = parameter;

      Comparitor = ColumnItemComparer;
      GroupedComparitor = GroupItemComparer;
    }

    /// <summary>
    /// Creates a copy of the given column
    /// </summary>
    /// <param name="columnToCopy"></param>
    public Column(Column columnToCopy)
      : this()
    {
      _name = columnToCopy._name;
      Comparitor = columnToCopy.Comparitor;
      _caption = columnToCopy._caption;
      _width = columnToCopy._width;
      _sortOrder = columnToCopy._sortOrder;
      _groupSortOrder = columnToCopy._groupSortOrder;
      _isVisible = columnToCopy._isVisible;
      _isGrouped = columnToCopy._isGrouped;
      ColumnItemAccessor = columnToCopy.ColumnItemAccessor;
      ColumnItemAccessorWithParameter = columnToCopy.ColumnItemAccessorWithParameter;
      GroupItemAccessorWithParameter = columnToCopy.GroupItemAccessorWithParameter;
      _groupItemValueAccessor = columnToCopy._groupItemValueAccessor;
      GroupedComparitor = columnToCopy.GroupedComparitor;
      HeaderIcon = columnToCopy.HeaderIcon;
      IsFixedWidth = columnToCopy.IsFixedWidth;
      ShowHeaderSortArrow = columnToCopy.ShowHeaderSortArrow;
      WrapText = columnToCopy.WrapText;
    }
    #endregion

    #region Object overrides
    public override string ToString()
    {
      return string.Format("SuperList Column: '{0}'", Name);
    }
    #endregion

    #region Implementation
    public virtual int ColumnItemComparer(object x, object y)
    {
      object cellItemX = null;
      object cellItemY = null;

      if (ColumnItemAccessor != null)
      {
        cellItemX = ColumnItemAccessor(x);
        cellItemY = ColumnItemAccessor(y);
      }
      else if (ColumnItemAccessorWithParameter != null)
      {
        cellItemX = ColumnItemAccessorWithParameter(x, AccessorParameter);
        cellItemY = ColumnItemAccessorWithParameter(y, AccessorParameter);
      }

      IComparable comparer = cellItemX as IComparable;
      if (cellItemX == null && cellItemY == null) return 0;
      else if (cellItemX == null) return -1;
      else if (cellItemY == null) return 1;
      else if (cellItemX.GetType().Equals(cellItemY.GetType())) return comparer.CompareTo(cellItemY);
      else return comparer.GetHashCode().CompareTo(cellItemY.GetHashCode());
    }

    public virtual int GroupItemComparer(object x, object y)
    {
      if (x == y)
      {
        return 0;
      }

      if (x != null && y != null)
      {
        if (GroupItemAccessorWithParameter != null)
        {
          if (GroupItemAccessorWithParameter(x, AccessorParameter) == GroupItemAccessorWithParameter(y, AccessorParameter))
          {
            return 0;
          }
        }
        else
        {
          if (GroupItemAccessor != null)
          {
            if (GroupItemAccessor(x) == GroupItemAccessor(y))
            {
              return 0;
            }
          }
          else if (ColumnItemAccessorWithParameter != null)
          {
            if (ColumnItemAccessorWithParameter(x, AccessorParameter) == ColumnItemAccessorWithParameter(y, AccessorParameter))
            {
              return 0;
            }
          }
        }
      }

      return Comparitor(x, y);
    }

    /// <summary>
    /// Called to fire a data changed event.
    /// </summary>
    /// <param name="whatChanged"></param>
    protected virtual void OnDataChanged(WhatPropertyChanged whatChanged)
    {
      if (DataChanged != null)
      {
        DataChanged(this, new ColumnDataChangedEventArgs(this, whatChanged));
      }
    }
    #endregion
  }
}